﻿using System;
using System.Data.Services.Client;
using System.Diagnostics.Contracts;
using System.Linq;
using CashFlow.Entities;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using System.Diagnostics;

namespace CashFlow.AzureRepository
{
	public class AzureRepositoryBase
	{
		protected string tenantId;
		protected static CloudStorageAccount storageAccount;

		/// <summary>
		/// Gets or sets the storage account.
		/// </summary>
		/// <value>The storage account.</value>
		public static CloudStorageAccount StorageAccount
		{
			set { storageAccount = value; }
		} 

		protected TableServiceContext db;

		#region Constructors
		/// <summary>
		/// Initializes the static members of the <see cref="T:AzureRepositoryBase"/> class.
		/// </summary>
		static AzureRepositoryBase()
		{
			try
			{
				storageAccount = GetStorageAccount();
			}
			catch (RoleEnvironmentException reExc)
			{
				if (storageAccount == null)
				{
					//TODO: vvs p2 log
					Trace.TraceError("TR0000000001. ERROR. Exception: {0}", reExc);
				}
			}
			catch (Exception ex)
			{
				//TODO: vvs p2 log
				Trace.TraceError("TR0000000001. ERROR. Exception: {0}", ex);
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="T:AzureRepositoryBase"/> class.
		/// </summary>
		public AzureRepositoryBase() : this (AppState.TenantId)
		{
		}

		public AzureRepositoryBase(string tenantId)
		{
			Contract.Requires(!string.IsNullOrEmpty(tenantId), "tenantId cannot be null");

			this.tenantId = tenantId;
			db = new TableServiceContext(storageAccount.TableEndpoint.ToString(), storageAccount.Credentials);
		}
		#endregion

		protected static CloudStorageAccount GetStorageAccount()
		{
			var connection = RoleEnvironment.GetConfigurationSettingValue("Data.ConnectionString");
			var account = CloudStorageAccount.Parse(connection);
			return account;
		}

		public string TenantId
		{
			get
			{
				return tenantId;
			}
			set
			{
				tenantId = value;
			}
		}


		#region Tables.
		public void Create(string tableName, TableServiceEntity entity)
		{
			db.AddObject(tableName, entity);
			//TODO: vvs p2 SaveChanges has overload - check it.
			db.SaveChanges();
		}

		public void Update(string tableName, TableServiceEntity entity, bool attach = true)
		{
			if (attach)
				db.AttachTo(tableName, entity, "*");

			db.UpdateObject(entity);
			try
			{
				//TODO: vvs p2 save with retry
				db.SaveChanges();
			}
			catch (Exception exc)
			{
				if (IsConcurrenceError(exc))
				{
					//TODO: vvs p2 retry on concurrency error (force to save)
				}
				else
				{
					throw;
				}
			}
		}

		public IQueryable<T> GetAllByKey<T>(string tableName, string pk) where T : TableServiceEntity
		{
			//TODO: vvs p3 exceptions.
			var table = db.CreateQuery<T>(tableName);
			var query = table.
				Where(b => b.PartitionKey == pk);
			return query;
		}

		public T GetByKeys<T>(string tableName, string pk, string rk) where T : TableServiceEntity
		{
			//TODO: vvs p3 exceptions.
			var table = db.CreateQuery<T>(tableName);
			var query = table.
				Where(b => b.PartitionKey == pk && b.RowKey == rk);
			var existingEntry = GetSingleRecord<T>(query);
			return existingEntry;
		}

		public T GetByKeys<T>(string tableName, T entity) where T : TableServiceEntity
		{
			return this.GetByKeys<T>(tableName, entity.PartitionKey, entity.RowKey);
		}

		public static T GetSingleRecord<T>(IQueryable<T> query)
		{
			try
			{
				return query.FirstOrDefault();
			}
			catch (DataServiceQueryException ex)
			{
				if (ex.InnerException.Message.Contains("ResourceNotFound") ||
						ex.Response.StatusCode == 404 ||
						ex.InnerException.Message.Contains("Input"))
				{
					return default(T);
				}
				throw;
			}
		}

		public bool IsConcurrenceError(Exception exc)
		{
			return exc.InnerException != null &&
						exc.InnerException is DataServiceClientException &&
						((DataServiceClientException)exc.InnerException).StatusCode == 412;
		}
		#endregion

	}
}
